package com.leaves.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.jwt.StpLogicJwtForStyle;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.json.JSONUtil;
import com.leaves.common.interceptor.AccessLimitIntercept;
import com.leaves.common.util.IpUtil;
import com.leaves.common.util.PrintRequest;
import com.leaves.response.ApiResult;
import com.leaves.response.ApiResultConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;


/**
 * @Author: LEAVES
 * @Version 1.0
 * @Date: 2022年04月01日 13时28分24秒
 * @Description: sa-token配置
 */
//@EnableWebMvc
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

    private static final Logger LOGGER = LoggerFactory.getLogger(SaTokenConfigure.class);

    // Sa-Token 整合 jwt (Style模式)
    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForStyle();
    }

    /**
     * 注册 [Sa-Token全局过滤器] 解决跨域问题
     */
    @Bean
    public SaServletFilter getSaServletFilter() {

        //拦截排除的路径集合
        List<String> list = new ArrayList<>();
        list.add("/**/v1/**");
        list.add("/**/error");
        return new SaServletFilter()
                // 指定 拦截路由 与 放行路由
                // 拦截地址
                .addInclude("/**")
                .setExcludeList(list)
                // 指定 拦截路由 与 放行路由
//                .addInclude("/**").addExclude("/favicon.ico")

                // 认证函数: 每次请求执行
                .setAuth(obj -> {
                    LOGGER.info("---------- 进入Sa-Token全局认证 -----------");
                    HttpServletRequest req = SpringMVCUtil.getRequest();
//                    printRequest(req);
                    PrintRequest.requestInfo(req);
                    // 登录认证 -- 拦截所有路由，并排除/user/doLogin 用于开放登录
                    SaRouter.match("/**", () -> StpUtil.checkLogin());

                })

                // 异常处理函数：每次认证函数发生异常时执行此函数
                .setError(e -> {
                    LOGGER.error("---------- 进入Sa-Token异常处理 -----------");
                    HttpServletResponse response = SpringMVCUtil.getResponse();
                    String message = "";
                    // 设置响应头
                    SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
                    LOGGER.error("异常信息 : " + e.getMessage());
                    if (e instanceof NotLoginException) {
                        response.setStatus(ApiResultConstant.CODE_NOT_LOGIN);
                        String type = ((NotLoginException) e).getType();
                        LOGGER.info("type = " + type);
                        String loginType = ((NotLoginException) e).getLoginType();
                        LOGGER.info("loginType = " + loginType);
                        if (NotLoginException.NOT_TOKEN.equals(type)) {
                            LOGGER.error("NotLoginException.NOT_TOKEN = " + NotLoginException.NOT_TOKEN);
                            message = "您的令牌为空";
                        } else if (NotLoginException.INVALID_TOKEN.equals(type)) {
                            LOGGER.error("NotLoginException.INVALID_TOKEN = " + NotLoginException.INVALID_TOKEN);
                            message = "您的令牌无效";
                        } else if (NotLoginException.TOKEN_TIMEOUT.equals(type)) {
                            LOGGER.error("NotLoginException.TOKEN_TIMEOUT = " + NotLoginException.TOKEN_TIMEOUT);
                            message = "您的令牌已过期";
                        } else if (NotLoginException.BE_REPLACED.equals(type)) {
                            LOGGER.error("NotLoginException.BE_REPLACED = " + NotLoginException.BE_REPLACED);
                            message = "账号在别处登录，请注意是否为您本人操作";
                        } else if (NotLoginException.KICK_OUT.equals(type)) {
                            LOGGER.error("NotLoginException.KICK_OUT = " + NotLoginException.KICK_OUT);
                            message = "您已被踢下线";
                        } else if (NotLoginException.KICK_OUT.equals(type)) {
                            LOGGER.error("NotLoginException.KICK_OUT = " + NotLoginException.KICK_OUT);
                            message = "您已被踢下线";
                        } else {
                            message = "当前会话未登录";
                        }
                    } else {
                        message = "系统异常，请稍再试！";
                    }
                    return JSONUtil.toJsonStr(ApiResult.checkLogin(message));
                })

                // 前置函数：在每次认证函数之前执行
                .setBeforeAuth(obj -> {
                    // ---------- 设置一些安全响应头 ----------
                    SaHolder.getResponse()
                            // 服务器名称
                            .setServer("yz")
                            // 是否可以在iframe显示视图： DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                            .setHeader("X-Frame-Options", "SAMEORIGIN")
                            // 是否启用浏览器默认XSS防护： 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时，停止渲染页面
                            .setHeader("X-XSS-Protection", "1; mode=block")
                            // 禁用浏览器内容嗅探
                            .setHeader("X-Content-Type-Options", "nosniff")
                            // ---------- 设置跨域响应头 ----------
                            // 允许指定域访问跨域资源
                            .setHeader("Access-Control-Allow-Origin", "*")
                            // 允许所有请求方式
                            .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
                            // 有效时间
                            .setHeader("Access-Control-Max-Age", "3600")
                            // 允许的header参数
                            .setHeader("Access-Control-Allow-Headers", "*");

                    // 如果是预检请求，则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .free(r -> LOGGER.info("--------OPTIONS预检请求，不做处理"))
                            .back();
                });
    }

    // 注册Sa-Token的拦截器
    //@Override
    //public void addInterceptors(InterceptorRegistry registry) {
    //    // 注册路由拦截器，自定义认证规则
    //    registry.addInterceptor(new SaRouteInterceptor((req, res, handler) -> {
    //
    //        // 登录认证 -- 拦截所有路由，并排除/user/doLogin 用于开放登录
    //        SaRouter.match("/api/**", "/api/login/*", r -> StpUtil.checkLogin());
    //
    //        // 角色认证 -- 拦截以 admin 开头的路由，必须具备 admin 角色或者 super-admin 角色才可以通过认证
    //        //SaRouter.match("/api/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin"));
    //
    //        // 权限认证 -- 不同模块认证不同权限
    //        SaRouter.match("/api/user/**", r -> StpUtil.checkPermission("user"));
    //    })).addPathPatterns("/**");
    //}

    /**
     * 注册 Sa-Token 的拦截器，打开注解式鉴权功能
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册注解拦截器，并排除不需要注解鉴权的接口地址 (与登录拦截器无关)
        registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(getAccessLimitIntercept()).addPathPatterns("/**");
    }

    /**
     * 这里需要先将限流拦截器入住，不然无法获取到拦截器中的redistemplate
     *
     * @return
     */
    @Bean
    public AccessLimitIntercept getAccessLimitIntercept() {
        return new AccessLimitIntercept();
    }
}

